package gek.uni.diplom.ocl.example.visitors;

import gek.uni.diplom.ocl.example.exceptions.ErrorException;
import gek.uni.diplom.ocl.example.exceptions.NotImplementedMethodException;
import gek.uni.diplom.ocl.example.exceptions.OperationNotFoundException;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.ecore.EcoreEnvironmentFactory;
import org.eclipse.ocl.ecore.internal.OCLStandardLibraryImpl;
import org.eclipse.ocl.expressions.BooleanLiteralExp;
import org.eclipse.ocl.expressions.ExpressionsFactory;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.OperationCallExp;
import org.eclipse.ocl.expressions.PropertyCallExp;
import org.eclipse.ocl.util.OCLStandardLibraryUtil;
import org.eclipse.ocl.utilities.PredefinedType;

/**
 * Утилитный класс, который разбивает логическое выражение на различные варианты
 * решения его частей, так, чтобы результирующее выражение возвращало true или
 * false. <br/>
 * 
 * Результатом этого посетителя будет массив списков списков OCL выражений. <br/>
 * 
 * По нулевому индексу результирующего массива можно обратится к списку списков
 * OperationCallExpression'ов типа Boolean, выполнение каждого из которых
 * гарантирует, что результат будет равен true.<br/>
 * По индексу 1 результирующего массива можно обратится к списку списков
 * OperationCallExpression'ов типа Boolean, выполнение каждого из которых
 * гарантирует, что результат будет равен false.<br/>
 * 
 * Так как значение true или false может быть достигнута различными способами,
 * то возвращается массив СПИСКОВ списков OperationCallExpression'ов. Каждый
 * список в этом СПИСКЕ содержит выражения, выполнение которых гарантирует
 * определенное значение результата.<br/>
 * 
 * 
 * 
 * @author arkady
 * 
 * @param <C>
 * @param <O>
 * @param <P>
 * @param <EL>
 * @param <PM>
 * @param <S>
 * @param <COA>
 * @param <SSA>
 * @param <CT>
 */
public class TrueFalseUtils<C, O, P, EL, PM, S, COA, SSA, CT> {

  private static TrueFalseUtils instance = new TrueFalseUtils();

  protected TrueFalseUtils() {

  }

  @SuppressWarnings("unchecked")
  public static TrueFalseUtils getInstance() {
    return instance;
  }

  /**
   * Основной метод класса, реализующий его задачи - генерация всех возможных
   * комбинаций значений условий, входящих в <code>boolExp</code>, при которых
   * (значениях) достигается определенная цель: результат boolExp равен true;
   * или false
   * 
   * По нулевому индексу результирующего массива можно обратится к списку
   * списков OperationCallExpression'ов типа Boolean, выполнение каждого из
   * которых гарантирует, что результат будет равен true.<br/>
   * По индексу 1 результирующего массива можно обратится к списку списков
   * OperationCallExpression'ов типа Boolean, выполнение каждого из которых
   * гарантирует, что результат будет равен false.<br/>
   * <br/>
   * 
   * <strong>Пример</strong></br>: boolExp - (a>0) and (c<0)<br/>
   * Результат:<br/>
   * [ [ [{a>0 = true},{c<0= true}] ], [ [{a>0 = true},{c<0 = false}], [{a>0 =
   * false},{c<0 = true}], {a>0 = false},{c<0 = false}] ]
   * 
   * 
   * 
   * @return
   * @throws OperationNotFoundException
   * @throws NotImplementedMethodException
   * @throws ErrorException
   */
  @SuppressWarnings({ "unchecked", "restriction" })
  public List<List<OperationCallExp<C, O>>>[] createTrueFalseExpressions(
      OCLExpression<C> boolExp) throws NotImplementedMethodException,
      OperationNotFoundException, ErrorException {

    // Проверяем, чтобы тип входного выражения был действительно Boolean
    if (false == boolExp.getType().equals(
        OCLStandardLibraryImpl.INSTANCE.getBoolean()))
      throw new ErrorException("Выражение " + boolExp.toString()
          + " должно быть типа Boolean");

    List<List<OperationCallExp<C, O>>>[] result = new ArrayList[2];

    List<List<OperationCallExp<C, O>>> listOfTrues = new ArrayList();
    List<List<OperationCallExp<C, O>>> listOfFalses = new ArrayList();
    result[0] = listOfTrues;
    result[1] = listOfFalses;

    if (boolExp instanceof BooleanLiteralExp) {
      OperationCallExp<C, O> boolEqualsTrue = createEqualTrueExpression(boolExp);
      OperationCallExp<C, O> boolEqualsFalse = createEqualFalseExpression(boolExp);

      List<OperationCallExp<C, O>> subList = new ArrayList<OperationCallExp<C, O>>();
      subList.add(boolEqualsTrue);
      result[0].add(subList);

      subList = new ArrayList<OperationCallExp<C, O>>();
      subList.add(boolEqualsFalse);
      result[1].add(subList);
      return result;
    }

    if (false == boolExp instanceof OperationCallExp)
      throw new NotImplementedMethodException(
          "Не реализована функциональность. см. стек вызовов");

    OperationCallExp<C, O> callExp = null;
    if (boolExp instanceof OperationCallExp) {
      callExp = (OperationCallExp<C, O>) boolExp;
      listOfTrues = createTrueExpressionsList(callExp);
      listOfFalses = createFalseExpressionsList(callExp);
      result[0] = listOfTrues;
      result[1] = listOfFalses;
    }
    return result;
  }

  /**
   * Создает на основе callExp систему равенств и неравенств, задающих
   * ограничения, при выполнении которых, callExp принимает значение true
   * 
   * @param callExp
   * @return
   * @throws NotImplementedMethodException
   * @throws OperationNotFoundException
   */
  @SuppressWarnings("unchecked")
  public List<List<OperationCallExp<C, O>>> createTrueExpressionsList(
      OperationCallExp<C, O> callExp) throws NotImplementedMethodException,
      OperationNotFoundException {
    List<List<OperationCallExp<C, O>>> result = new ArrayList<List<OperationCallExp<C, O>>>();

    OperationCallExp<C, O> left = null;
    if (callExp.getSource() instanceof OperationCallExp)
      left = (OperationCallExp<C, O>) callExp.getSource();

    else if (callExp.getSource() instanceof PropertyCallExp)
      left = createEqualFalseExpression(callExp);

    else if (callExp instanceof BooleanLiteralExp)
      left = createEqualTrueExpression(callExp);

    OperationCallExp<C, O> right = null;

    try {
      if (callExp.getArgument().size()>0){
        if ( callExp.getArgument().get(0) instanceof OperationCallExp)
  
          right = (OperationCallExp<C, O>) callExp.getArgument().get(0);
        else if (callExp.getArgument().get(0) instanceof PropertyCallExp)
          right = createEqualFalseExpression(callExp);
  
        else if (callExp.getArgument().get(0) instanceof BooleanLiteralExp)
          right =
              (OperationCallExp<C, O>) EcoreUtil.copy(callExp);//createEqualTrueExpression(callExp.getArgument().get(0));
      }
    } catch (Exception e) {
      System.out.println(e.toString());
    }

    List<List<OperationCallExp<C, O>>> subList = null;

    int opCode = callExp.getOperationCode();
    switch (opCode) {
    case PredefinedType.EQUAL:
    case PredefinedType.LESS_THAN:
    case PredefinedType.LESS_THAN_EQUAL:
    case PredefinedType.GREATER_THAN:
    case PredefinedType.GREATER_THAN_EQUAL:
    case PredefinedType.OCL_AS_TYPE:
    case PredefinedType.OCL_IS_IN_STATE:
    case PredefinedType.OCL_IS_INVALID:
    case PredefinedType.OCL_IS_KIND_OF:
    case PredefinedType.OCL_IS_NEW:
    case PredefinedType.OCL_IS_TYPE_OF:
    case PredefinedType.OCL_IS_UNDEFINED:
    case PredefinedType.INCLUDES:
    case PredefinedType.INCLUDES_ALL:
    case PredefinedType.EXCLUDES:
    case PredefinedType.EXCLUDES_ALL:
    case PredefinedType.HAS_RETURNED:
    case PredefinedType.IS_SIGNAL_SENT:
    case PredefinedType.IS_OPERATION_CALL:
    case PredefinedType.IS_EMPTY:
    case PredefinedType.NOT_EMPTY:
    case PredefinedType.IS_UNIQUE:
      List<OperationCallExp<C, O>> childList = new ArrayList<OperationCallExp<C, O>>();
      childList.add(createEqualTrueExpression(callExp));
      result.add(childList);
      break;
    case PredefinedType.AND:
      // Добавляем решения оба true
      subList = createTrueExpressionsList(left);
      for (List<OperationCallExp<C, O>> leftInnerList : subList) {
        for (List<OperationCallExp<C, O>> rightInnerList : createTrueExpressionsList(right)) {
          leftInnerList.addAll(rightInnerList);
        }
      }
      result.addAll(subList);
      break;
    case PredefinedType.OR:
      // Добавляем решения оба true
      subList = createTrueExpressionsList(left);
      for (List<OperationCallExp<C, O>> leftInnerList : subList) {
        for (List<OperationCallExp<C, O>> rightInnerList : createTrueExpressionsList(right)) {
          leftInnerList.addAll(rightInnerList);
        }
      }
      result.addAll(subList);

      // Добавляем решения первый - true, второй - false
      subList = createTrueExpressionsList(left);
      for (List<OperationCallExp<C, O>> leftInnerList : subList) {
        for (List<OperationCallExp<C, O>> rightInnerList : createFalseExpressionsList(right)) {
          leftInnerList.addAll(rightInnerList);
        }
      }
      result.addAll(subList);

      // Добавляем решения первый - false, второй - true
      subList = createFalseExpressionsList(left);
      for (List<OperationCallExp<C, O>> leftInnerList : subList) {
        for (List<OperationCallExp<C, O>> rightInnerList : createTrueExpressionsList(right)) {
          leftInnerList.addAll(rightInnerList);
        }
      }
      result.addAll(subList);
    default:
      throw new NotImplementedMethodException(callExp.getName());
    }
    return result;
  }

  /**
   * Создает на основе callExp систему равенств и неравенств, задающих
   * ограничения, при выполнении которых, callExp принимает значение true
   * 
   * @param callExp
   * @return
   * @throws NotImplementedMethodException
   * @throws OperationNotFoundException
   */
  public List<List<OperationCallExp<C, O>>> createFalseExpressionsList(
      OperationCallExp<C, O> callExp) throws NotImplementedMethodException,
      OperationNotFoundException {
    List<List<OperationCallExp<C, O>>> result = new ArrayList<List<OperationCallExp<C, O>>>();

    OperationCallExp<C, O> left = null;
    OperationCallExp<C, O> right = null;

    int opCode = callExp.getOperationCode();
    switch (opCode) {
    case PredefinedType.EQUAL:
    case PredefinedType.LESS_THAN:
    case PredefinedType.LESS_THAN_EQUAL:
    case PredefinedType.GREATER_THAN:
    case PredefinedType.GREATER_THAN_EQUAL:
    case PredefinedType.OCL_AS_TYPE:
    case PredefinedType.OCL_IS_IN_STATE:
    case PredefinedType.OCL_IS_INVALID:
    case PredefinedType.OCL_IS_KIND_OF:
    case PredefinedType.OCL_IS_NEW:
    case PredefinedType.OCL_IS_TYPE_OF:
    case PredefinedType.OCL_IS_UNDEFINED:
    case PredefinedType.INCLUDES:
    case PredefinedType.INCLUDES_ALL:
    case PredefinedType.EXCLUDES:
    case PredefinedType.EXCLUDES_ALL:
    case PredefinedType.HAS_RETURNED:
    case PredefinedType.IS_SIGNAL_SENT:
    case PredefinedType.IS_OPERATION_CALL:
    case PredefinedType.IS_EMPTY:
    case PredefinedType.NOT_EMPTY:
    case PredefinedType.IS_UNIQUE:
      List<OperationCallExp<C, O>> childList = new ArrayList<OperationCallExp<C, O>>();
      childList.add(createEqualFalseExpression(callExp));
      result.add(childList);
      break;
    case PredefinedType.AND:

      if (callExp.getSource() instanceof OperationCallExp)
        left = (OperationCallExp<C, O>) callExp.getSource();

      else if (callExp.getSource() instanceof PropertyCallExp)
        left = createEqualFalseExpression(callExp);

      else if (callExp instanceof BooleanLiteralExp)
        left = createEqualFalseExpression(callExp);
      // else
      // throw new NotImplementedMethodException("createFalseExpressionList("
      // + callExp.getName() + ")");

      try {
        if (callExp.getArgument().get(0) instanceof OperationCallExp)
          right = (OperationCallExp<C, O>) callExp.getArgument().get(0);

        else if (callExp.getArgument().get(0) instanceof PropertyCallExp)
          right = createEqualFalseExpression(callExp);

        else if (callExp.getArgument().get(0) instanceof BooleanLiteralExp)
          right = createEqualFalseExpression(callExp.getArgument().get(0));
      } catch (Exception e) {
        System.out.println(e.toString());
      }
      // else
      // throw new NotImplementedMethodException("createFalseExpressionList("
      // + callExp.getArgument().get(0) + ")");

      // Добавляем решения оба false
      List<List<OperationCallExp<C, O>>> subList = null;
      subList = createFalseExpressionsList(left);
      for (List<OperationCallExp<C, O>> leftInnerList : subList) {
        for (List<OperationCallExp<C, O>> rightInnerList : createFalseExpressionsList(right)) {
          leftInnerList.addAll(rightInnerList);
        }
      }
      result.addAll(subList);

      // Добавляем решения первый - true, второй - false
      subList = createTrueExpressionsList(left);
      for (List<OperationCallExp<C, O>> leftInnerList : subList) {
        for (List<OperationCallExp<C, O>> rightInnerList : createFalseExpressionsList(right)) {
          leftInnerList.addAll(rightInnerList);
        }
      }
      result.addAll(subList);

      // Добавляем решения первый - false, второй - true
      subList = createFalseExpressionsList(left);
      for (List<OperationCallExp<C, O>> leftInnerList : subList) {
        for (List<OperationCallExp<C, O>> rightInnerList : createTrueExpressionsList(right)) {
          leftInnerList.addAll(rightInnerList);
        }
      }
      result.addAll(subList);
      break;
    case PredefinedType.OR:
      // Добавляем решения оба false
      subList = createFalseExpressionsList(left);
      for (List<OperationCallExp<C, O>> leftInnerList : subList) {
        for (List<OperationCallExp<C, O>> rightInnerList : createFalseExpressionsList(right)) {
          leftInnerList.addAll(rightInnerList);
        }
      }
      result.addAll(subList);
    default:
      throw new NotImplementedMethodException(callExp.getName());
    }
    return result;
  }

  /**
   * 
   * @param exp
   *          - выражение типа Boolean
   * @return {@link OperationCallExp} типа {@link Boolean} вида {exp = true}
   * @throws OperationNotFoundException
   */
  public OperationCallExp<C, O> createEqualTrueExpression(OCLExpression<C> exp)
      throws OperationNotFoundException {
    return createEqualSomeBoolExpression(exp, true);
  }

  /**
   * 
   * @param exp
   *          - выражение типа Boolean
   * @return {@link OperationCallExp} типа {@link Boolean} вида {exp = false}
   * @throws OperationNotFoundException
   */
  public OperationCallExp<C, O> createEqualFalseExpression(OCLExpression<C> exp)
      throws OperationNotFoundException {
    return createEqualSomeBoolExpression(exp, false);
  }

  /**
   * @param exp
   *          - выражение типа Boolean
   * @return {@link OperationCallExp} типа {@link Boolean} вида {exp = value}
   * @throws OperationNotFoundException
   */
  @SuppressWarnings( { "unchecked", "restriction" })
  public OperationCallExp<C, O> createEqualSomeBoolExpression(
      OCLExpression<C> callExp, Boolean value)
      throws OperationNotFoundException {
    assert (callExp.getType().equals(OCLStandardLibraryImpl.INSTANCE
        .getBoolean()));

    OperationCallExp<C, O> result = ExpressionsFactory.eINSTANCE
        .createOperationCallExp();
    List<EOperation> operations = OCLStandardLibraryUtil
        .createBooleanOperations(EcoreEnvironmentFactory.INSTANCE
            .createEnvironment());
    EOperation resOp = null;
    for (EOperation op : operations) {
      int opCode = OCLStandardLibraryUtil.getOperationCode(op.getName());
      if (opCode == PredefinedType.EQUAL) {
        resOp = op;
        break;
      }
    }

    if (resOp == null)
      throw new OperationNotFoundException(OCLStandardLibraryUtil
          .getOperationName(PredefinedType.EQUAL), callExp.getType().toString());

    result.setType(callExp.getType());
    result.setReferredOperation((O) resOp);
    result.setName(resOp.getName());
    result.setSource((OCLExpression<C>) EcoreUtil.copy(callExp));

    BooleanLiteralExp<C> booleanLiteral = ExpressionsFactory.eINSTANCE
        .createBooleanLiteralExp();
    booleanLiteral.setType((C) OCLStandardLibraryImpl.INSTANCE.getBoolean());
    booleanLiteral.setBooleanSymbol(value);
    result.getArgument().add(booleanLiteral);
    return result;
  }

  public OperationCallExp<C, O> createBooleanEqualsTrue(
      BooleanLiteralExp<C> literal) throws OperationNotFoundException {
    return createEqualTrueExpression(literal);
  }
}
